// // Copyright (c) 2009 All Right Reserved // // vl // // 2009-01-01 // Contains ... namespace LargoCommon.Music { using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Contracts; using System.Linq; using JetBrains.Annotations; /// /// Harmonic Analyzer. /// public sealed class HarmonicAnalyzer { #region Fields /// /// Musical Block. /// private readonly MusicalBody musicalBody; /// /// Harmonic motive number. /// private int harMotiveNumber; /// /// Covered bars by harmony. /// private BitArray coveredBars; #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// The given musical body. public HarmonicAnalyzer(MusicalBody givenMusicalBody) { this.musicalBody = givenMusicalBody; } /// /// Initializes a new instance of the class. /// [UsedImplicitly] public HarmonicAnalyzer() { } #endregion #region Properties /// /// Gets or sets Musical Form. /// private HarmonicModel HarmonicModel { get; set; } #endregion #region Public methods - Harmonic Analyze /// /// Extract Harmonical Motive. /// /// The harmonic model. /// Harmonic Analyze Type. /// /// Returns value. /// public IEnumerable AnalyzeHarmony(HarmonicModel harmonicModel, HarmonicAnalysisType analyzeType) { this.HarmonicModel = harmonicModel; var harmonicStream = new HarmonicStream(harmonicModel.Header); //// "Derived from core" //// this.HarmonicStreamAnalyzer.ExtractHarmonicStreamByTicks(); var changes = new List(); var lastModalityCode = string.Empty; switch (analyzeType) { case HarmonicAnalysisType.DivisionByTicks: { foreach (var bar in this.musicalBody.Bars) { var harmonicBar = bar.HarmonicBar; if (harmonicBar == null) { continue; } harmonicStream.HarmonicBars.Add(harmonicBar); var code = harmonicBar.GetHarmonicModalityCode; if (code == lastModalityCode) { continue; } var tc = new TonalityChange(bar.BarNumber) { HarmonicModalityCode = code }; changes.Add(tc); lastModalityCode = code; } var harmonicChanges = this.ExtractHarmonicalMotive(harmonicStream); changes.AddRange(harmonicChanges); } break; } return changes; } /// /// Finds the repeated harmonic motive. /// /// The harmonic stream. /// Returns value. private HarmonicChange FindRepeatedHarmonicMotive(HarmonicStream harmonicStream) { Contract.Requires(harmonicStream != null); const int minMotiveLength = 3; var numberOfBars = harmonicStream.HarmonicBars.Count; for (var barIdx = 0; barIdx < numberOfBars; barIdx++) { if (barIdx >= this.coveredBars.Length) { continue; } if (this.coveredBars[barIdx]) { continue; } var harmonicBar = harmonicStream.HarmonicBars.ElementAt(barIdx); if (harmonicBar == null) { continue; } for (var nextIdx = barIdx + 1; nextIdx < numberOfBars; nextIdx++) { if (nextIdx >= this.coveredBars.Count) { //// 2017/02 error break; } if (this.coveredBars[nextIdx]) { continue; } var nextBar = harmonicStream.HarmonicBars.ElementAt(nextIdx); if (nextBar == null) { continue; } //// Test if next repetition exists var existsRepeatedMotive = string.CompareOrdinal(harmonicBar.UniqueIdentifier, nextBar.UniqueIdentifier) == 0 && harmonicStream.EqualSegments(barIdx + 1, nextIdx + 1, minMotiveLength - 1); if (!existsRepeatedMotive) { continue; } //// Determine Length of the repeated motive var lengthOfMotive = harmonicStream.LengthOfMotive(barIdx, nextIdx); lengthOfMotive = Math.Min(lengthOfMotive, nextIdx - barIdx); this.harMotiveNumber++; var harMotive = HarmonicMotive.GetNewHarmonicMotive(harmonicStream.Header, this.harMotiveNumber); harmonicStream.WriteToMotive(harMotive, barIdx, lengthOfMotive); //// , 1 var change = new HarmonicChange(barIdx + 1) { HarmonicMotive = harMotive }; //// change.BlockModel = this.MusicalBlock.SourceMusicalBlockModel; for (var b = barIdx; b < barIdx + lengthOfMotive; b++) { if (b < this.coveredBars.Length) { this.coveredBars[b] = true; } } //// break; return change; } } return null; } /// /// Occupies the stream by harmonic motive. /// /// The harmonic stream. /// The given change. /// Returns value. private IEnumerable OccupyStreamByHarmonicMotive(HarmonicStream harmonicStream, HarmonicChange givenChange) { Contract.Requires(harmonicStream != null); Contract.Requires(givenChange != null); var harmonicChanges = new Collection { givenChange }; var numberOfBars = harmonicStream.HarmonicBars.Count; var barIdx = givenChange.BarNumber - 1; var harmonicBar = harmonicStream.HarmonicBars.ElementAt(barIdx); if (harmonicBar == null) { return harmonicChanges; } var nextBars = Math.Min(numberOfBars, this.coveredBars.Count); for (var nextIdx = barIdx + 1; nextIdx < nextBars; nextIdx++) { if (this.coveredBars[nextIdx]) { continue; } var nextBar = harmonicStream.HarmonicBars.ElementAt(nextIdx); if (nextBar == null) { continue; } //// Test if next repetition exists var existsRepeatedMotive = string.CompareOrdinal(harmonicBar.UniqueIdentifier, nextBar.UniqueIdentifier) == 0 && harmonicStream.EqualSegments(barIdx + 1, nextIdx + 1, givenChange.HarmonicMotive.Length - 1); if (!existsRepeatedMotive) { continue; } var change = new HarmonicChange(nextIdx + 1) { HarmonicMotive = givenChange.HarmonicMotive }; //// change.BlockModel = this.MusicalBlock.SourceMusicalBlockModel; harmonicChanges.Add(change); for (var b = nextIdx; b < nextIdx + givenChange.HarmonicMotive.Length; b++) { if (b < this.coveredBars.Length) { this.coveredBars[b] = true; } } } return harmonicChanges; } /// /// Extract Harmonical Motive. /// /// The harmonic stream. /// /// Returns value. /// private IEnumerable ExtractHarmonicalMotive(HarmonicStream harmonicStream) { //// cyclomatic complexity 10:15 var numberOfBars = this.musicalBody.Context.Header.NumberOfBars; this.harMotiveNumber = 0; this.coveredBars = new BitArray(numberOfBars); var harmonicChanges = new List(); var finished = false; HarmonicChange change; while (!finished) { change = this.FindRepeatedHarmonicMotive(harmonicStream); if (change != null) { this.HarmonicModel.AddMotive(change.HarmonicMotive); var changes = this.OccupyStreamByHarmonicMotive(harmonicStream, change); harmonicChanges.AddRange(changes); } else { finished = true; } } for (var barIdx = 0; barIdx < numberOfBars; barIdx++) { if (this.coveredBars[barIdx]) { continue; } var nextIdx = barIdx; while (nextIdx < numberOfBars && !this.coveredBars[nextIdx]) { this.coveredBars[nextIdx] = true; nextIdx++; } var lengthOfMotive = nextIdx - barIdx; this.harMotiveNumber++; var harMotive = HarmonicMotive.GetNewHarmonicMotive(harmonicStream.Header, this.harMotiveNumber); harmonicStream.WriteToMotive(harMotive, barIdx, lengthOfMotive); //// , 1 this.HarmonicModel.AddMotive(harMotive); change = new HarmonicChange(barIdx + 1) { HarmonicMotive = harMotive }; //// change.BlockModel = this.MusicalBlock.SourceMusicalBlockModel; harmonicChanges.Add(change); } return new Collection(harmonicChanges); } #endregion } }